//! trace log 实现
//!
//! 由于一些特殊要求需要和系统匹配, 单独实现的 log 日志系统,
//! 日志记录是在系统的 log 文件中并且在指定本地文件后 log 可以同步到本地文件中
#![feature(lazy_cell)]
#![feature(extract_if)]
#![allow(unsafe_code)]
#![allow(clippy::string_add)]
#![allow(clippy::cast_lossless)]
#![allow(clippy::cast_possible_truncation)]

use std::{
    env::temp_dir,
    fs::{self, OpenOptions},
    path::PathBuf,
    sync::{
        atomic::{AtomicU32, Ordering},
        Arc, Mutex,
    },
};

use dfs::{
    dfs_read,
    inode::{DfsInode, DirCreateOptions},
    params::{ParamCreateOptions, ParamOperation},
    seq_file::{SeqFileCreateOptions, SeqFileOperation},
    seq_print,
};
use manage::{SiminkManage, SiminkQuitCause};

mod trace;
pub use ctor::ctor;
pub use trace::*;
pub use tracelog_macro::tracelog;

// 0 faltal 严重错误，系统无法恢复，需要停机
// 1 error  错误输出，能够恢复，系统还能运行
// 2 warn   警告输出
// 3 info   基本信息输出
// 4 debug  调试输出
// 5 log    都会输出
// 6 unimpl 未实现
// 7 guest error 客户机错误
static LOG_LEVEL: AtomicU32 = AtomicU32::new(0);
static LOG_RECORD: Mutex<String> = Mutex::new(String::new());
static mut MANAGE: Option<Arc<SiminkManage>> = None;

/// 日志系统初始化
///
/// 需要在程序退出前手动调用`tracelog_exit`已便于做一些清理操作, 包括同步本地文件
#[allow(clippy::missing_panics_doc)]
pub fn tracelog_init(sys: Arc<DfsInode>, manage: Arc<SiminkManage>) {
    let log_dir =
        DirCreateOptions::new().read_write(true).name("log").create_dir(sys.clone()).unwrap();

    let path = Arc::new(FilePathSet::default());
    ParamCreateOptions::new()
        .read_write(true)
        .name("log_file_path")
        .create_param_file(log_dir.clone(), path)
        .unwrap();

    ParamCreateOptions::new()
        .read_write(true)
        .name("level")
        .create_param_file(log_dir.clone(), Arc::new(ParamLevel))
        .unwrap();

    unsafe {
        // 全局只有这一次赋值, 可以确保访问是安全的
        MANAGE = Some(manage);
    }

    SeqFileCreateOptions::new()
        .read_write(true)
        .name("log_record")
        .create_seq_file(log_dir, Arc::new(LogRecord))
        .unwrap();

    trace_init(sys);
}

/// 日志系统退出
///
/// 需要在程序退出前调用, 用于清理内部数据
#[allow(clippy::missing_panics_doc)]
pub fn tracelog_exit() {
    let mut buf = String::new();
    let size = dfs_read("/sys/log/log_file_path", &mut buf).unwrap();
    if size != 0 {
        let path = PathBuf::from(buf);
        let lock = LOG_RECORD.lock().unwrap();
        fs::write(path, lock.as_bytes()).unwrap();
    }
    trace_exit();
}

struct ParamLevel;

impl ParamOperation for ParamLevel {
    fn read(&self) -> String {
        LOG_LEVEL.load(Ordering::Relaxed).to_string()
    }

    fn wrtie(&self, buf: &str) -> dfs::DfsResult<()> {
        let level = buf.parse::<u32>().map_err(|_| dfs::DfsError::InvalidArgs)?;
        LOG_LEVEL.store(level, Ordering::Relaxed);
        Ok(())
    }
}

#[derive(Debug, Default)]
struct FilePathSet {
    path: Mutex<PathBuf>,
}

impl ParamOperation for FilePathSet {
    fn read(&self) -> String {
        let lock = self.path.lock().unwrap();
        lock.to_str().unwrap().to_string()
    }

    fn wrtie(&self, buf: &str) -> dfs::DfsResult<()> {
        let path = if buf.is_empty() { temp_dir().join("simink.log") } else { PathBuf::from(buf) };

        let res = OpenOptions::new().read(true).write(true).create(true).open(&path);
        match res {
            Ok(_) => {
                let mut lock = self.path.lock().unwrap();
                *lock = path;
                Ok(())
            },
            Err(_) => Err(dfs::DfsError::InvalidArgs),
        }
    }
}

struct LogRecord;

impl SeqFileOperation for LogRecord {
    fn show(
        &self,
        m: &mut dfs::seq_file::SeqFile,
        _: &mut Box<dyn std::any::Any + Send>,
    ) -> dfs::DfsResult<()> {
        seq_print!(m, "{}", LOG_RECORD.lock().unwrap());
        Ok(())
    }

    fn write(&self, buf: &str, _: &mut u64) -> dfs::DfsResult<usize> {
        if buf.len() <= 2 {
            return Err(dfs::DfsError::InvalidArgs);
        }
        let (split1, split2) = buf.split_at(2);
        let level = split1[..1].parse::<u32>().map_err(|_| dfs::DfsError::InvalidArgs)?;
        log_record(level, split2.to_string());
        Ok(split2.len())
    }

    fn size(&self) -> usize {
        LOG_RECORD.lock().unwrap().len()
    }
}

/// 记录日志
///
/// 当记录级别为 faltal 时, 系统将会退出
#[allow(clippy::missing_panics_doc)]
pub fn log_record(level: u32, log: String) {
    if level == 5 || level <= LOG_LEVEL.load(Ordering::Relaxed) {
        let l = log + "\n";
        let mut lock = LOG_RECORD.lock().unwrap();
        lock.push_str(&l);
    }
    if level == 0 {
        unsafe {
            if let Some(m) = &MANAGE {
                m.request_simink_quit(SiminkQuitCause::LogRecordFaltal);
            }
        }
    }
}

/// 记录客户机错误
#[macro_export]
macro_rules! log_guest_error {
    ($($arg:tt)*) => {
        $crate::log_record(7, format!($($arg)*));
    };
}

/// 记录功能未实现
#[macro_export]
macro_rules! log_unimp {
    ($($arg:tt)*) => {
        $crate::log_record(6, format!($($arg)*));
    };
}

/// 记录所有日志
///
/// 不论当前级别是什么, 都会记录到日志中
#[macro_export]
macro_rules! log_all {
    ($($arg:tt)*) => {
        $crate::log_record(5, format!($($arg)*));
    };
}

/// 记录 debug 日志
#[macro_export]
macro_rules! log_debug {
    ($($arg:tt)*) => {
        $crate::log_record(4, format!($($arg)*));
    };
}

/// 记录 info 日志
#[macro_export]
macro_rules! log_info {
    ($($arg:tt)*) => {
        $crate::log_record(3, format!($($arg)*));
    };
}

/// 记录 warn 日志
#[macro_export]
macro_rules! log_warn {
    ($($arg:tt)*) => {
        $crate::log_record(2, format!($($arg)*));
    };
}

/// 记录 error 日志
#[macro_export]
macro_rules! log_error {
    ($($arg:tt)*) => {
        $crate::log_record(1, format!($($arg)*));
    };
}

/// 记录 faltal 日志
///
/// 该记录将会导致系统退出
#[macro_export]
macro_rules! log_faltal {
    ($($arg:tt)*) => {
        $crate::log_record(0, format!($($arg)*));
    };
}
